/*
 *   Copyright (c) 2024 Huawei Device Co., Ltd.
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
#include "audioRecorder.h"
#include <bits/alltypes.h>
#include "config.h"
#include "dfx/log/av_codec_sample_log.h"
#include "dfx/error/av_codec_sample_error.h"

#undef LOG_TAG
#define LOG_TAG "AuidoRecorder"

namespace {
using namespace std::chrono_literals;
FILE *g_file = nullptr;
int32_t g_inputFrameBytes = Config::GetInstance().GetAudioSampleRate() * 0.02 * sizeof(short);  // 20ms
}

AudioRecorder::~AudioRecorder()
{
    AVCODEC_SAMPLE_LOGI("~AudioRecorder");
    StartRelease();
}

int32_t AudioRecorder::Init(SampleInfo &sampleInfo)
{
    // 按传入的配置，初始化编码模块
    std::lock_guard<std::mutex> lock(mutex_);
    CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started.");
    CHECK_AND_RETURN_RET_LOG(audioEncoder_ == nullptr,
                             AVCODEC_SAMPLE_ERR_ERROR, "Already started.");

    sampleInfo_ = sampleInfo;

    audioEncoder_ = std::make_unique<AudioEncoder>();

    int32_t ret = audioEncoder_->Create(sampleInfo_.audioCodecMime);
    CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create audio encoder(%{public}s) failed",
        sampleInfo_.audioCodecMime.c_str());
    AVCODEC_SAMPLE_LOGI("Create audio encoder(%{public}s)", sampleInfo_.audioCodecMime.c_str());

    audioEncContext_ = new CodecUserData;
    ret = audioEncoder_->Config(sampleInfo_, audioEncContext_);
    CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Encoder config failed");

    g_file = fopen(Config::GetInstance().GetPCMPath().c_str(), "rb");

    releaseThread_ = nullptr;
    isReleased_ = false;
    
    AVCODEC_SAMPLE_LOGI("Succeed");
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t AudioRecorder::Start(std::shared_ptr<Muxer> &muxer)
{
    std::lock_guard<std::mutex> lock(mutex_);
    AVCODEC_SAMPLE_LOGI("Enter Start");
    CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started.");
    CHECK_AND_RETURN_RET_LOG(audioEncContext_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR,
                             "audioEncContext_ is nullptr.");
    CHECK_AND_RETURN_RET_LOG(audioEncoder_ != nullptr,
                             AVCODEC_SAMPLE_ERR_ERROR, "Already started.");

    muxer_ = muxer;

    int32_t ret = audioEncoder_->Start();
    CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Audio Encoder start failed");
    isStarted_ = true;
    audioEncInputThread_ = std::make_unique<std::thread>(&AudioRecorder::AudioEncInputThread, this);
    audioEncOutputThread_ = std::make_unique<std::thread>(&AudioRecorder::AudioEncOutputThread, this);
    if (audioEncInputThread_ == nullptr || audioEncOutputThread_ == nullptr) {
        AVCODEC_SAMPLE_LOGE("Create thread failed");
        StartRelease();
        return AVCODEC_SAMPLE_ERR_ERROR;
    }

    AVCODEC_SAMPLE_LOGI("Succeed");
    return AVCODEC_SAMPLE_ERR_OK;
}

void AudioRecorder::StartRelease()
{
    if (!isReleased_) {
        AVCODEC_SAMPLE_LOGI("Start release CodecTest");
        isReleased_ = true;
        Release();
    }
}

void AudioRecorder::ReleaseThread()
{
    isStarted_ = false;
    if (audioEncInputThread_ && audioEncInputThread_->joinable()) {
        audioEncInputThread_->detach();
        audioEncInputThread_.reset();
    }
    if (audioEncOutputThread_ && audioEncOutputThread_->joinable()) {
        audioEncOutputThread_->detach();
        audioEncOutputThread_.reset();
    }
}

void AudioRecorder::ReleaseResource()
{
    if (muxer_ != nullptr) {
        muxer_->Release();
        muxer_.reset();
        AVCODEC_SAMPLE_LOGI("Muxer release successful");
    }
    if (audioEncoder_ != nullptr) {
        audioEncoder_->Stop();
        audioEncoder_->Release();
        audioEncoder_.reset();
        AVCODEC_SAMPLE_LOGI("Audio encoder release successful");
    }
    if (audioEncContext_ != nullptr) {
        delete audioEncContext_;
        audioEncContext_ = nullptr;
    }
    if (g_file) {
        fclose(g_file);
    }
}

void AudioRecorder::Release()
{
    std::lock_guard<std::mutex> lock(mutex_);
    AVCODEC_SAMPLE_LOGI("Release");
    ReleaseThread();
    ReleaseResource();
    doneCond_.notify_all();
    
    if (sampleInfo_.encodeDoneCallback != nullptr) {
        sampleInfo_.encodeDoneCallback(sampleInfo_.encodeDoneCallbackData);
    }
    AVCODEC_SAMPLE_LOGI("Succeed");
}

int32_t AudioRecorder::WaitForDone()
{
    AVCODEC_SAMPLE_LOGI("Wait called");
    std::unique_lock<std::mutex> lock(mutex_);
    doneCond_.wait(lock);
    if (releaseThread_ && releaseThread_->joinable()) {
        releaseThread_->join();
        releaseThread_.reset();
    }
    AVCODEC_SAMPLE_LOGI("Done");
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t AudioRecorder::Stop()
{
    if (isStarted_) {
        StartRelease();
        return WaitForDone();
    }
    return 0;
}

void AudioRecorder::AudioEncInputThread()
{
    while (true) {
        CHECK_AND_BREAK_LOG(isStarted_, "Encoder input thread out");
        std::unique_lock<std::mutex> lock(audioEncContext_->inputMutex);
        bool condRet = audioEncContext_->inputCond.wait_for(
            lock, 5s, [this]() { return !isStarted_ || (!audioEncContext_->inputBufferInfoQueue.empty()); });
        CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out");
        CHECK_AND_CONTINUE_LOG(!audioEncContext_->inputBufferInfoQueue.empty(),
                               "Buffer queue is empty, continue, cond ret: %{public}d", condRet);

        CodecBufferInfo bufferInfo = audioEncContext_->inputBufferInfoQueue.front();
        audioEncContext_->inputBufferInfoQueue.pop();
        audioEncContext_->inputFrameCount++;
        lock.unlock();

        // 拿到输入buffer的内存地址，读取pcm数据到输入buffer
        uint8_t *inputBufferAddr = OH_AVBuffer_GetAddr(reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer));
        if (fread(inputBufferAddr, g_inputFrameBytes, 1, g_file) != 1) {
            AVCODEC_SAMPLE_LOGE("buffer fwrite err");
            bufferInfo.attr.size = 0;
            bufferInfo.attr.flags = AVCODEC_BUFFER_FLAGS_EOS;
        } else {
            bufferInfo.attr.size = g_inputFrameBytes;
            bufferInfo.attr.flags = AVCODEC_BUFFER_FLAGS_NONE;
        }
        int32_t ret = audioEncoder_->PushInputData(bufferInfo);
        CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Push data failed, thread out");
        CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Catch EOS, thread out");
    }
}

void AudioRecorder::AudioEncOutputThread()
{
    while (true) {
        CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out");
        std::unique_lock<std::mutex> lock(audioEncContext_->outputMutex);
        bool condRet = audioEncContext_->outputCond.wait_for(
            lock, 5s, [this]() { return !isStarted_ || !audioEncContext_->outputBufferInfoQueue.empty(); });
        CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out");
        CHECK_AND_CONTINUE_LOG(!audioEncContext_->outputBufferInfoQueue.empty(),
                               "Buffer queue is empty, continue, cond ret: %{public}d", condRet);

        CodecBufferInfo bufferInfo = audioEncContext_->outputBufferInfoQueue.front();
        audioEncContext_->outputBufferInfoQueue.pop();
        CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Catch EOS, thread out");
        audioEncContext_->outputFrameCount++;
        AVCODEC_SAMPLE_LOGW("Out buffer count: %{public}u, size: %{public}d, flag: %{public}u, pts: %{public}" PRId64,
                            audioEncContext_->outputFrameCount, bufferInfo.attr.size, bufferInfo.attr.flags,
                            bufferInfo.attr.pts);
        lock.unlock();

        bufferInfo.attr.pts = 0;

        muxer_->WriteAudioSample(reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer), bufferInfo.attr);
        int32_t ret = audioEncoder_->FreeOutputData(bufferInfo.bufferIndex);
        CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Encoder output thread out");
    }
    AVCODEC_SAMPLE_LOGI("Exit, frame count: %{public}u", audioEncContext_->inputFrameCount);
    StartRelease();
}